package com.cxmx.escort.aspect.core;

import com.cxmx.escort.aspect.api.AbstractApiGrpcFactory;
import com.cxmx.escort.aspect.api.ProtocolApi;
import com.cxmx.escort.aspect.api.RedWineClient;
import com.cxmx.escort.aspect.baseApi.ApiGrpc;
import com.cxmx.escort.aspect.baseApi.Request;
import com.cxmx.escort.aspect.baseApi.Response;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.grpc.ManagedChannel;
import io.grpc.StatusRuntimeException;
import io.grpc.stub.StreamObserver;

import java.util.Map;
import java.util.concurrent.TimeUnit;

public class RedWineClientImpl implements RedWineClient {
    private final ManagedChannel mChannel;
    private ApiGrpc.ApiBlockingStub mBlockingStub;
    private ApiGrpc.ApiStub mAsyncStub;
    private AbstractApiGrpcFactory mApiGrpcFactory;

    public interface RedWineResultCallback {
        void onResult(RedWineExaminationData data);

        void onError(Throwable t);
    }

    public RedWineClientImpl(AbstractApiGrpcFactory apiGrpcFactory, String host, int port) {
        mApiGrpcFactory = apiGrpcFactory;
        mChannel = apiGrpcFactory.createManagedChannel(host, port);
        mBlockingStub = apiGrpcFactory.createApiBlockingStub(mChannel);
        mAsyncStub = apiGrpcFactory.createApiStub(mChannel);
    }

    @Override
    public void shutdown() throws InterruptedException {
        mChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }

    @Override
    public void examineRedWineAsync(Map<RedWineProperty, Float> wineData,
                                    final RedWineResultCallback resultCallback) {
        validateData(wineData);

        StreamObserver<Response> streamObserver = new StreamObserver<Response>() {
            @Override
            public void onNext(Response value) {
                resultCallback.onResult(processResponse(value));
            }

            @Override
            public void onError(Throwable t) {
                resultCallback.onError(t);
            }

            @Override
            public void onCompleted() {

            }
        };
        mAsyncStub.examineWine(generateRequest(wineData), streamObserver);
    }

    private Request generateRequest(Map<RedWineProperty, Float> wineData) {
        JsonObject jsonRequest = new JsonObject();
        JsonArray wineDataArray = new JsonArray();
        RedWineProperty[] values = RedWineProperty.values();
        for (int i = 0; i < values.length; i++) {
            Float val = wineData.get(values[i]);
            if (val == null) {
                val = 0f;
            }
            wineDataArray.add(val);
        }
        jsonRequest.add(ProtocolApi.RequestKey.INPUT, wineDataArray);
        return mApiGrpcFactory.createRequestBuilder()
                .setJsonRequest(jsonRequest.toString())
                .build();
    }

    private RedWineExaminationData processResponse(Response response) {
        JsonObject jsonResponse = new JsonParser().parse(response.getJsonResponse())
                .getAsJsonObject();

        JsonArray graphsJArr = jsonResponse.getAsJsonArray(ProtocolApi.ResponseKey.GRAPHS);
        ProtocolApi.GraphInfo[] graphInfos = new ProtocolApi.GraphInfo[graphsJArr == null ? 0 : graphsJArr.size()];
        for (int i = 0; i < graphInfos.length; i++) {
            JsonObject graphJObj = graphsJArr.get(i).getAsJsonObject();
            String url = graphJObj.get(ProtocolApi.ResponseKey.GraphKey.URL).getAsString();
            String name = graphJObj.get(ProtocolApi.ResponseKey.GraphKey.NAME).getAsString();
            graphInfos[i] = new ProtocolApi.GraphInfo(name, url);
        }

        return new RedWineExaminationData(
                jsonResponse.get(ProtocolApi.ResponseKey.PREDICTED_LABEL).getAsString(),
                jsonResponse.get(ProtocolApi.ResponseKey.CONFIDENCE_SCORE).getAsFloat(),
                graphInfos
        );
    }

    @Override
    public RedWineExaminationData examineRedWine(Map<RedWineProperty, Float> wineData)
            throws IllegalArgumentException, StatusRuntimeException {
        validateData(wineData);
        Response response = mBlockingStub.examineWine(generateRequest(wineData));
        return processResponse(response);
    }

    private void validateData(Map<RedWineProperty, Float> wineData) throws IllegalArgumentException {
        RedWineProperty[] values = RedWineProperty.values();
        int numExtras = 2;
        int matchTotalMin = values.length - numExtras;
        int matchTotalMax = values.length;
        int matchCount = 0;
        for (int i = 0; i < values.length; i++) {
            if (wineData.containsKey(values[i])) {
                matchCount++;
            }
        }
        if (matchCount < matchTotalMin || matchCount > matchTotalMax) {
            throw new IllegalArgumentException("Invalid red wine data, does not contain all red wine properties");
        }
    }
}
